/// \file
/// \brief Base class that implements the networking for the lobby server. Database specific functionality is implemented in a derived class.
///
/// This file is part of RakNet Copyright 2003 Kevin Jenkins.
///
/// Usage of RakNet is subject to the appropriate license agreement.
/// Creative Commons Licensees are subject to the
/// license found at
/// http://creativecommons.org/licenses/by-nc/2.5/
/// Single application licensees are subject to the license found at
/// http://www.jenkinssoftware.com/SingleApplicationLicense.html
/// Custom license users are subject to the terms therein.
/// GPL license users are subject to the GNU General Public
/// License as published by the Free
/// Software Foundation; either version 2 of the License, or (at your
/// option) any later version.

#ifndef __RAKNET_LOBBY_SERVER_H
#define __RAKNET_LOBBY_SERVER_H

#include "LobbyPCTypes.h"
#include "PluginInterface.h"
#include "DS_OrderedList.h"
#include "LobbyDBSpec.h"
#include "RakNetTypes.h"
#include "LobbyTypes.h"
#include "PacketPriority.h"
#include "TitleValidationDBSpec.h"
#include "DS_Table.h"
#include "LobbyPCCommon.h"

namespace RakNet
{
	struct LobbyServerUser;
	struct LobbyRoomsTableContainer;
}

// This can't be in the namespace or VS8 compiler bugs won't find the implementation and say it is an undefined external.
int RAK_DLL_EXPORT UserCompBySysAddr( SystemAddress const &key, RakNet::LobbyServerUser * const &data );
int RAK_DLL_EXPORT UserCompById( LobbyDBSpec::DatabaseKey const &key, RakNet::LobbyServerUser * const &data );

namespace RakNet
{

struct LobbyServerUser;

/// Implements the basis for the lobby server for RakNet, independent of any kind of database.
/// Features include user to user interaction, chatting, rooms, and quick match.
/// Just attach as a plugin and go.
/// If you want to interact with the implemented PostgreSQL database, such as adding users or titles, see LobbyServerPostgreSQL
class RAK_DLL_EXPORT LobbyServer :
	public PluginInterface
{
public:
	LobbyServer();
	virtual ~LobbyServer();

	// ------------ PC SPECIFIC --------------
	/// Ordering channel to use with RakPeer::Send()
	void SetOrderingChannel(char oc);
	void SetSendPriority(PacketPriority pp);


	struct PotentialFriend
	{
		LobbyDBSpec::DatabaseKey invitor;
		LobbyDBSpec::DatabaseKey invitee;
	};

protected:
	void Login_NetMsg(Packet *packet);
	void SetTitleLoginId_NetMsg(Packet *packet);
	void Logoff_NetMsg(Packet *packet);
	void RegisterAccount_NetMsg(Packet *packet);
	void UpdateAccount_NetMsg(Packet *packet);
	void ChangeHandle_NetMsg(Packet *packet);
	void RetrievePasswordRecoveryQuestion_NetMsg(Packet *packet);
	void RetrievePassword_NetMsg(Packet *packet);
	void DownloadFriends_NetMsg(Packet *packet);
	void SendAddFriendInvite_NetMsg(Packet *packet);
	void AcceptAddFriendInvite_NetMsg(Packet *packet);
	void DeclineAddFriendInvite_NetMsg(Packet *packet);
	void DownloadIgnoreList_NetMsg(Packet *packet);
	void UpdateIgnoreList_NetMsg(Packet *packet);
	void RemoveFromIgnoreList_NetMsg(Packet *packet);
	void DownloadEmails_NetMsg(Packet *packet);
	void SendEmail_NetMsg(Packet *packet);
	void DeleteEmail_NetMsg(Packet *packet);
	void UpdateEmailStatus_NetMsg(Packet *packet);
	void SendIM_NetMsg(Packet *packet);
	void CreateRoom_NetMsg(Packet *packet);
	void DownloadRooms_NetMsg(Packet *packet);
	void LeaveRoom_NetMsg(Packet *packet);
	void JoinRoom_NetMsg(Packet *packet);
	void JoinRoomByFilter_NetMsg(Packet *packet);
	void RoomChat_NetMsg(Packet *packet);
	void SetReadyToPlayStatus_NetMsg(Packet *packet);
	void InviteToRoom_NetMsg(Packet *packet);
	void KickRoomMember_NetMsg(Packet *packet);
	void SetRoomIsHidden_NetMsg(Packet *packet);
	void SetRoomAllowSpectators_NetMsg(Packet *packet);
	void ChangeNumSlots_NetMsg(Packet *packet);
	void GrantModerator_NetMsg(Packet *packet);
	void StartGame_NetMsg(Packet *packet);
	void SubmitMatch_NetMsg(Packet *packet);
	void DownloadRating_NetMsg(Packet *packet);
	void QuickMatch_NetMsg(Packet *packet);
	void CancelQuickMatch_NetMsg(Packet *packet);
	void DownloadActionHistory_NetMsg(Packet *packet);
	void AddToActionHistory_NetMsg(Packet *packet);

	virtual LobbyDBSpec::CreateUser_Data* AllocCreateUser_Data(void) const=0;
	virtual LobbyDBSpec::UpdateUser_Data* AllocUpdateUser_Data(void) const=0;
	virtual LobbyDBSpec::SendEmail_Data* AllocSendEmail_Data(void) const=0;
	virtual LobbyDBSpec::AddToActionHistory_Data* AllocAddToActionHistory_Data(void) const=0;
	virtual RankingServerDBSpec::SubmitMatch_Data* AllocSubmitMatch_Data(void) const=0;
	virtual RankingServerDBSpec::GetRatingForParticipant_Data* AllocGetRatingForParticipant_Data(void) const=0;

	virtual void Login_DBQuery(SystemAddress systemAddress, const char *userIdStr, const char *userPw)=0;
	virtual void RetrievePasswordRecoveryQuestion_DBQuery(SystemAddress systemAddress, const char *userIdStr)=0;
	virtual void RetrievePassword_DBQuery(SystemAddress systemAddress, const char *userIdStr, const char *passwordRecoveryAnswer)=0;
	virtual void GetFriends_DBQuery(SystemAddress systemAddress, LobbyDBSpec::DatabaseKey userId, bool ascendingSort)=0;
	virtual void AddFriend_DBQuery(LobbyDBSpec::DatabaseKey id, LobbyDBSpec::DatabaseKey friendId, const char *messageBody, unsigned char language)=0;
	virtual void CreateUser_DBQuery(SystemAddress systemAddress, LobbyDBSpec::CreateUser_Data* input)=0;
	virtual void UpdateUser_DBQuery(LobbyDBSpec::UpdateUser_Data* input)=0;
	virtual void UpdateHandle_DBQuery(const char *newHandle, LobbyDBSpec::DatabaseKey userId)=0;
	virtual void GetIgnoreList_DBQuery(SystemAddress systemAddress, LobbyDBSpec::DatabaseKey myId, bool ascendingSort)=0;
	virtual void AddToIgnoreList_DBQuery(bool hasUserId, LobbyDBSpec::DatabaseKey userId, const char *userHandle, LobbyDBSpec::DatabaseKey myId, const char *actionsStr)=0;
	virtual void RemoveFromIgnoreList_DBQuery(LobbyDBSpec::DatabaseKey unignoredUserId, LobbyDBSpec::DatabaseKey myId)=0;
	virtual void GetEmails_DBQuery(SystemAddress systemAddress, LobbyDBSpec::DatabaseKey myId, bool inbox, bool ascendingSort)=0;
	virtual void SendEmail_DBQuery(LobbyDBSpec::SendEmail_Data *input)=0;
	virtual void DeleteEmail_DBQuery(LobbyDBSpec::DatabaseKey myId, LobbyDBSpec::DatabaseKey emailId)=0;
	virtual void UpdateEmailStatus_DBQuery(LobbyDBSpec::DatabaseKey myId, LobbyDBSpec::DatabaseKey emailId, int newStatus, bool wasOpened)=0;
	virtual void DownloadActionHistory_DBQuery(SystemAddress systemAddress, bool ascendingSort, LobbyDBSpec::DatabaseKey myId)=0;
	virtual void AddToActionHistory_DBQuery(SystemAddress systemAddress, LobbyDBSpec::AddToActionHistory_Data* input)=0;
	virtual void SubmitMatch_DBQuery(SystemAddress systemAddress, RankingServerDBSpec::SubmitMatch_Data* input)=0;
	virtual void GetRating_DBQuery(SystemAddress systemAddress, RankingServerDBSpec::GetRatingForParticipant_Data* input)=0;

	virtual void Login_DBResult(bool dbSuccess, const char *queryErrorMessage, SystemAddress systemAddress, LobbyDBSpec::GetUser_Data *res, const char *userPw_in);
	virtual void AddFriend_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::AddFriend_Data *res, const char *messageBody, unsigned char language);
	virtual void RetrievePasswordRecoveryQuestion_DBResult(bool dbSuccess, const char *queryErrorMessage, SystemAddress systemAddress, LobbyDBSpec::GetUser_Data *res);
	virtual void RetrievePassword_DBResult(bool dbSuccess, const char *queryErrorMessage, SystemAddress systemAddress, LobbyDBSpec::GetUser_Data *res, const char *passwordRecoveryAnswer);
	virtual void GetFriends_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::GetFriends_Data *res, SystemAddress systemAddress);
	virtual void CreateUser_DBResult(bool dbSuccess, bool querySuccess, const char *queryErrorMessage, SystemAddress systemAddress, LobbyDBSpec::CreateUser_Data *res);
	virtual void UpdateHandle_DBResult(LobbyDBSpec::ChangeUserHandle_Data *res);
	virtual void GetIgnoreList_DBResult(bool dbSuccess, const char *queryErrorMessage, SystemAddress systemAddress, LobbyDBSpec::GetIgnoreList_Data *res);
	virtual void AddToIgnoreList_DBResult(bool dbSuccess, bool querySuccess, const char *queryErrorMessage, LobbyDBSpec::DatabaseKey ignoredUserId, LobbyDBSpec::DatabaseKey myId, LobbyDBSpec::AddToIgnoreList_Data *res);
	virtual void RemoveFromIgnoreList_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::DatabaseKey unignoredUserId, LobbyDBSpec::DatabaseKey myId);
	virtual void SendEmail_DBResult(bool dbSuccess, bool querySuccess, const char *queryErrorMessage, LobbyDBSpec::SendEmail_Data *res);
	virtual void GetEmails_DBResult(bool dbSuccess, bool querySuccess, const char *queryErrorMessage, LobbyDBSpec::GetEmails_Data *res);
	virtual void DeleteEmail_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::DatabaseKey myId, LobbyDBSpec::DeleteEmail_Data *res);
	virtual void UpdateEmailStatus_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::DatabaseKey myId, LobbyDBSpec::UpdateEmailStatus_Data *res);
	virtual void AddToActionHistory_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::AddToActionHistory_Data *res, SystemAddress systemAddress);
	virtual void GetActionHistory_DBResult(bool dbSuccess, const char *queryErrorMessage, LobbyDBSpec::GetActionHistory_Data *res, SystemAddress systemAddress);
	virtual void SubmitMatch_DBResult(bool dbSuccess, const char *queryErrorMessage, RankingServerDBSpec::SubmitMatch_Data *res, SystemAddress systemAddress);
	virtual void GetRatingForParticipant_DBResult(bool dbSuccess, const char *queryErrorMessage, RankingServerDBSpec::GetRatingForParticipant_Data *res, SystemAddress systemAddress);


	// Plugin interface functions
	virtual void OnAttach(RakPeerInterface *peer);
	PluginReceiveResult OnReceive(RakPeerInterface *peer, Packet *packet);
	virtual void OnCloseConnection(RakPeerInterface *peer, SystemAddress systemAddress);
	virtual void OnShutdown(RakPeerInterface *peer);
	virtual void Update(RakPeerInterface *peer);

	// Utility functions
	bool Send( const RakNet::BitStream * bitStream, SystemAddress target );

	bool SendIM(LobbyServerUser *sender, LobbyServerUser *recipient, const char *messageBody, unsigned char language);

	// 0 = new friend, implicitly online (+ data), 1 = no longer friends, 2 = friend online, 3 = friend offline

	enum FriendStatusChange
	{
		FSC_NEW_FRIEND,
		FSC_NO_LONGER_FRIENDS,
		FSC_FRIEND_ONLINE,
		FSC_FRIEND_OFFLINE,
	};
	void NotifyFriendStatusChange(SystemAddress target, LobbyDBSpec::DatabaseKey friendId, const char *friendHandle, unsigned char status);

	// TODO - run nat punchthrough directly, or separately?

	void Clear(void);
	void ClearTitles(void);
	void ClearLobbyGames(void);
	void ClearUsers(void);

	RakPeerInterface *rakPeer;
	void DestroyRoom(LobbyRoom *room);
	virtual void RemoveUser(SystemAddress sysAddr);
	void RemoveRoomMember(LobbyRoom *room, LobbyDBSpec::DatabaseKey memberId, bool *roomIsDead, bool wasKicked, SystemAddress memberAddr);
	void RemoveRoom(LobbyRoom *room);
	void SendToRoomMembers(LobbyRoom *room, RakNet::BitStream *bs, bool hasSkipId=false, LobbyDBSpec::DatabaseKey skipId=0);
	void ForceAllRoomMembersToLobby(LobbyRoom *room);
	LobbyServerUser* GetUserByHandle(const char *handle);
	LobbyServerUser* GetUserByAddress(SystemAddress sysAddr);
	LobbyServerUser* GetUserByID(LobbyDBSpec::DatabaseKey id);

	void RemoveFromQuickMatchList(LobbyServerUser *user);
	void SerializeClientSafeCreateUserData(LobbyServerUser *user, RakNet::BitStream *bs);

	// Double sort for fast lookup by either system address or id
	DataStructures::OrderedList<SystemAddress, LobbyServerUser*, UserCompBySysAddr> usersBySysAddr;
	DataStructures::OrderedList<LobbyDBSpec::DatabaseKey, LobbyServerUser*, UserCompById> usersByID;
	// Does not store pointers, just references them in users
	DataStructures::List<LobbyServerUser*> quickMatchUsers;

	// Rooms under particular games
	DataStructures::OrderedList< TitleValidationDBSpec::DatabaseKey, LobbyRoomsTableContainer*, LobbyTitleCompByDatabaseKey> lobbyTitles;

	LobbyRoom* GetLobbyRoomByTitle(const char *title, TitleValidationDBSpec::DatabaseKey gameId);
	LobbyRoom* GetLobbyRoomByRowId(LobbyClientRoomId rowId, TitleValidationDBSpec::DatabaseKey gameId);
	LobbyRoomsTableContainer* GetLobbyRoomsTableContainerById(TitleValidationDBSpec::DatabaseKey titleId);

	TitleValidationDBSpec::AddTitle_Data* GetTitleDataByPassword(char *titlePassword, int titlePasswordLength);
	TitleValidationDBSpec::AddTitle_Data* GetTitleDataById(TitleValidationDBSpec::DatabaseKey titleId);
	void SendToRoomNotIgnoredNotSelf(LobbyServerUser* lsu, RakNet::BitStream *reply);

	char orderingChannel;
	PacketPriority packetPriority;
	DataStructures::List<TitleValidationDBSpec::AddTitle_Data*> titles;
	DataStructures::List<RankingServerDBSpec::ModifyTrustedIPList_Data*> trustedIpList;

	bool IsTrustedIp(SystemAddress systemAddress, TitleValidationDBSpec::DatabaseKey titleId);

	void SendNewMemberToRoom(LobbyServerUser *lsu, LobbyRoom* room, bool joinAsSpectator);
	void FlagEnteredAndSerializeRoom(LobbyServerUser *lsu, RakNet::BitStream &reply, LobbyRoom* room);
	LobbyServerUser * LobbyServer::DeserializeHandleOrId(char *userHandle, LobbyClientUserId &userId, RakNet::BitStream *bs);

	bool IsInPotentialFriends(PotentialFriend p);
	void AddToPotentialFriends(PotentialFriend p);
	void RemoveFromPotentialFriends(LobbyDBSpec::DatabaseKey user);
	void RemoveFromPotentialFriends(PotentialFriend p);
	DataStructures::List<PotentialFriend> potentialFriends;
};


struct LobbyServerUser
{
	LobbyServerUser();
	~LobbyServerUser();
	LobbyDBSpec::GetUser_Data *data;
	SystemAddress systemAddress;
	RakNetTime connectionTime;
	RakNet::LobbyUserStatus status;
	bool downloadedFriends;
	bool readyToPlay;
	RakNetTime quickMatchStopWaiting;
	int quickMatchUserRequirement;
	TitleValidationDBSpec::AddTitle_Data *title;
	DataStructures::List<LobbyDBSpec::AddFriend_Data *> friends;
	DataStructures::List<LobbyDBSpec::AddToIgnoreList_Data *> ignoredUsers;
	LobbyRoom *currentRoom;

	bool AddFriend(LobbyDBSpec::AddFriend_Data *_friend);
	void RemoveFriend(LobbyDBSpec::DatabaseKey friendId);
	bool IsFriend(LobbyDBSpec::DatabaseKey friendId);

	bool AddIgnoredUser(LobbyDBSpec::AddToIgnoreList_Data *ignoredUser);
	void RemoveIgnoredUser(LobbyDBSpec::DatabaseKey userId);
	bool IsIgnoredUser(LobbyDBSpec::DatabaseKey userId);

	LobbyDBSpec::DatabaseKey GetID(void) const;
};


}

#endif

